using System;
using System.Collections.Generic;
using System.ComponentModel;
using Granite.Metadata;

namespace Granite.Axml
{
	public class BindingFragment
	{
		public event EventHandler ValueChanged;

		readonly object m_Source;
		readonly PropertyMetadata m_CurrentProperty;
		readonly string m_NextBindingPath;

		//This is created and destroyed as needed
		BindingFragment m_NextFragment;

		bool m_IsActive;

		public BindingFragment(object source, string bindingPath)
		{
			m_Source = source;

			string currentPropertyName;
			if (bindingPath.Contains("."))
			{
				int breakPoint = bindingPath.IndexOf(".");
				currentPropertyName = bindingPath.Substring(0, breakPoint);
				
				var metaData = MetadataCache.GetMetadata(source.GetType());
				if(!metaData.Properties.Contains (currentPropertyName))
					throw new Exception("Cannot find property named " + currentPropertyName + " on object of type " + source.GetType ().Name);
				
				m_CurrentProperty = metaData.Properties[currentPropertyName];
				
				
				m_NextBindingPath = bindingPath.Substring(breakPoint + 1);
			}
			else
			{
				m_CurrentProperty = MetadataCache.GetMetadata(source.GetType()).Properties[bindingPath];
			}

			Activate();
		}

		public void Activate()
		{
			if (m_IsActive)
				return;

			if (m_Source is INotifyPropertyChanged)
				((INotifyPropertyChanged)m_Source).PropertyChanged += DataContext_PropertyChanged;
			
			m_IsActive = true;
			
			if (m_NextBindingPath != null)
				NextFragment = new BindingFragment(ReadProperty(), m_NextBindingPath);
		}

		public void Deactivate()
		{
			if (!m_IsActive)
				return;

			m_IsActive = false;
			
			if (m_Source is INotifyPropertyChanged)
				((INotifyPropertyChanged)m_Source).PropertyChanged -= DataContext_PropertyChanged;

			if (NextFragment != null)
			{
				NextFragment.Deactivate();
				NextFragment = null;
			}
		}

		private void DataContext_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (NextFragment != null)
			{
				NextFragment.Deactivate();
				NextFragment = new BindingFragment(ReadProperty(), m_NextBindingPath);
			}

			if (ValueChanged != null)
				ValueChanged(this, EventArgs.Empty);
		}

		public object ReadValue()
		{
			if (!m_IsActive)
				throw new InvalidOperationException("Cannot read value, the binding fragment is not active");

			if (NextFragment != null)
				return NextFragment.ReadValue();
			else
				return ReadProperty();
		}

		private object ReadProperty()
		{
			return m_CurrentProperty.InvokeGet(m_Source);
		}

		private BindingFragment NextFragment
		{
			get { return m_NextFragment; }
			set
			{
				if (m_NextFragment != null)
					m_NextFragment.ValueChanged -= NextFragment_ValueChanged;

				m_NextFragment = value;

				if (m_NextFragment != null)
					m_NextFragment.ValueChanged += NextFragment_ValueChanged;
			}
		}

		void NextFragment_ValueChanged(object sender, EventArgs e)
		{
			if (ValueChanged != null)
				ValueChanged(this, EventArgs.Empty);
		}


		internal void WriteValue(object value)
		{
			if (!m_IsActive)
				throw new InvalidOperationException("Cannot write value, the binding fragment is not active");

			if (NextFragment != null)
				NextFragment.WriteValue(value);
			else
				WriteProperty(value);
		}

		private void WriteProperty(object value)
		{
			var temp = ReadProperty();

			if (!Object.Equals(value, temp))
				m_CurrentProperty.InvokeSet(m_Source, value);
		}
	}
}
